package controller

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"

	"gddgame.cc/galaxy/binocle"
	. "gddgame.cc/galaxy/common/dsl/game"
	. "gddgame.cc/galaxy/common/dsl/game/ai_auto"
	. "gddgame.cc/galaxy/common/dsl/game/attr"
	. "gddgame.cc/galaxy/common/dsl/game/badge"
	. "gddgame.cc/galaxy/common/dsl/game/base"
	. "gddgame.cc/galaxy/common/dsl/game/config"
	. "gddgame.cc/galaxy/common/dsl/game/group"
	. "gddgame.cc/galaxy/common/dsl/game/item"
	. "gddgame.cc/galaxy/common/dsl/game/object"
	. "gddgame.cc/galaxy/common/dsl/game/reward"
	. "gddgame.cc/galaxy/common/dsl/game/skill"
	. "gddgame.cc/galaxy/common/dsl/game/stat"
	. "gddgame.cc/galaxy/common/dsl/game/target"
	. "gddgame.cc/galaxy/common/dsl/game/task"
	. "gddgame.cc/galaxy/common/dsl/game/user"
	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/common/namespaces"
	"gddgame.cc/galaxy/common/service/playbook"
	"gddgame.cc/galaxy/common/service/playbook/game_user"
	"gddgame.cc/galaxy/core/sdk"
	"gddgame.cc/galaxy/project/star/star/game"
	"gddgame.cc/galaxy/project/star/star/model"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

var (
	Center *game.Center

	Env *Environment
)

func bindData(context def.Context, center *game.Center) error {
	var err error

	// config
	if _, err := GetConfigData(Env, model.InitConfig); err != nil {
		return err
	}

	// stat
	if center.StatData, err = GetStatData(Env); err != nil {
		return err
	}

	// group
	if center.GroupData, err = GetGroupData(Env); err != nil {
		return err
	}

	// attr
	if center.AttrData, err = GetAttrData(Env); err != nil {
		return err
	}
	// skill
	if center.SkillData, err = GetSkillData(Env); err != nil {
		return err
	}

	// user
	if center.UserData, err = GetUserData(Env); err != nil {
		return nil
	}

	// badge
	if center.BadgeData, err = GetBadgeData(Env, nil, reflect.TypeOf(&model.BadgeInside{})); err != nil {
		return err
	}

	// Ai
	if center.AiData, err = GetAiData(Env); err != nil {
		return err
	}

	// element
	if center.BaseData, err = GetBaseData(Env); err != nil {
		return err
	}
	if center.ObjectData, err = GetObjectData(Env, reflect.TypeOf(&model.ObjectExtend{}), nil, center.BaseData, center.GroupData); err != nil {
		return err
	}
	if center.TargetData, err = GetTargetData(Env, center.AttrData, center.GroupData, center.AiData, center.BaseData, center.ObjectData); err != nil {
		return err
	}
	// item
	if center.ItemData, err = GetItemData(Env, nil, reflect.TypeOf(&model.ItemInside{}), center.GroupData); err != nil {
		return err
	}

	if center.TaskData, err = GetTaskData(Env, nil, reflect.TypeOf(&model.TaskInside{}), center.ItemData); err != nil {
		return err
	}
	// task
	var tasks []model.SettingTask
	_, err = model.SettingTaskModel.Query().Find(&tasks)
	if err != nil {
		return err
	}
	center.Tasks = make(map[int]*model.SettingTask, len(tasks))
	for k := range tasks {
		center.Tasks[tasks[k].Id] = &tasks[k]
	}

	// center
	center.Compute()

	return nil
}

func InitGame(builder composer.Builder, app binocle.App, client *sdk.AppClient) error {
	builder.Infof("Init Game")
	Env = NewEnvironment(builder, app.Name(), client, builder.Cache(app.FullName()))
	Env.SetApp(app)
	if err := Env.RegisterModule(model.Provider); err != nil {
		return err
	}
	Env.RegisterInit(func(env *Environment) error {
		c := game.NewCenter(Env)

		if err := bindData(builder, c); err != nil {
			return err
		}
		Center = c
		return nil
	})
	Env.RegisterInit(func(env *Environment) error {
		return LoadZone(builder)
	})
	return Env.Start()
}

func StartPlaybook(client *sdk.AppClient, app binocle.App, instance *namespaces.RoomInstance) (*playbook.Playbook, error) {
	// 获取用户信息
	token := instance.Token()
	users := instance.Members()
	openIds := make([]string, 0, len(users))
	for key, _ := range users {
		openIds = append(openIds, key)
	}

	var scene playbook.Scene
	var zone string
	setting := instance.Setting()
	mapId := setting["mapId"].(string)
	manager := game_user.NewPlayerManager(Env, Center.UserData, Center)
	manager.SetTaskData(Center.TaskData)

	if strings.HasPrefix(mapId, "zone:") {
		zone = strings.Replace(mapId, "zone:", "", 1)
		node, err := GetZoneNode(client, zone)
		if err != nil {
			return nil, err
		}
		scene = game.NewZoneScene(Center, manager, node)
	} else {
		return nil, errors.New("scene is error")
	}
	pb := playbook.NewPlaybook(token, scene, openIds, app)
	scene.Bind(pb)
	pb.OnStart(func(players []*playbook.Player) {
		// 记录用户战斗进度
		for _, player := range players {
			_ = Env.UpdateUserStatus(player.OpenId, RoomKey, token)
		}
	})
	pb.OnExit(func(player *playbook.Player) {
		_ = Env.UpdateUserStatus(player.OpenId, RoomKey, "")
		data := make(map[string]interface{})
		// 记录最新信息
		var user model.User
		_ = Center.FindUser(player.OpenId, &user)

		u := manager.GetUserBase(player.OpenId).(*model.User)
		user.Items = u.Items
		user.ItemMap = u.ItemMap
		_ = UpdateItems(client, &user, data)

		if err := UpdateUser(client, manager, player, &user, data); err != nil {
			client.Error(err)
		}
	})
	pb.OnFinish(func(players []*playbook.Player) {

		people := len(players)
		ids := make([]string, 0, len(players))
		members := make(map[string]interface{})
		// 战斗时长
		for _, player := range players {
			_ = Env.UpdateUserStatus(player.OpenId, RoomKey, "")
			if player.Exit {
				continue
			}
			ids = append(ids, player.OpenId)

			data := make(map[string]interface{})
			// 记录最新信息
			var user model.User
			_ = Center.FindUser(player.OpenId, &user)

			// 保存剩余任务进度
			expire := ZoneNextTime()
			if err := Center.SaveTaskProcess(user.Id, player.RealTaskProcess, int(expire)); err != nil {
				client.Error(err)
			}
			// 记录战斗结果
			userGame, _ := FinishZoneGame(client, people, pb.StartTime(), player, &user, int(expire))
			members[player.OpenId] = userGame.Id

			result := player.Result.(*model.GameResult)
			reward := &Reward{}
			reward.MergeReward(result.Reward)
			reward.MergeReward(result.TaskReward)
			if err := ReceiveReward(client, &user, reward, data); err != nil {
				client.Error(err)
			}

			u := manager.GetUserBase(player.OpenId).(*model.User)
			user.Items = u.Items
			user.ItemMap = u.ItemMap
			_ = UpdateItems(client, &user, data)

			if err := UpdateUser(client, manager, player, &user, data); err != nil {
				client.Error(err)
			}
		}
		// 传递记录id
		instance.OnFinish(members)
		if len(ids) > 0 {
			// 更新Zone信息: battle事件
			if zone != "" {
				success := scene.(*game.ZoneScene).IsSuccess()
				_ = BattleZone(client, zone, success)
			}
		}
	})

	return pb, nil
}

func FinishZoneGame(context def.Context, people int, startTime *time.Time, player *playbook.Player, user *model.User, expire int) (*UserGame, error) {
	var err error
	userGame := UserGame{
		UserId:    user.Id,
		Type:      model.ZoneGame,
		People:    people,
		Result:    *player.Result.(*model.GameResult),
		StartTime: startTime,
	}
	if err := Center.InsertUserGame(&userGame, expire); err != nil {
		goto Rollback
	}
	return &userGame, nil
Rollback:
	return nil, SystemExceptionDetail("更新用户战斗记录失败", err)
}

func UpdateItems(context def.Context, user *model.User, data map[string]interface{}) error {
	var err error
	// 更新道具信息
	err = Env.SingleClean("user/item/"+utils.ToString(user.Id), func() error {
		t := []int{}
		for _, key := range user.Items {
			item, _ := user.ItemMap[key]
			if !item.Change {
				t = append(t, item.Id)
				continue
			}
			if item.Number == 0 {
				// 删除该道具
				if _, err = model.UserItemModel.Query().Where("=", "id", item.Id).Delete(); err != nil {
					return SystemExceptionDetail("删除使用道具失败", err)
				}
			} else {
				t = append(t, item.Id)
				if _, err = model.UserItemModel.Query().Save(false, item); err != nil {
					fmt.Println(err)
					return SystemExceptionDetail("消耗道具失败", err)
				}
			}
		}
		if len(t) < len(user.ItemMap) {
			data["items"] = t
		}
		return nil
	}, true)

	return nil
}

func UpdateUser(context def.Context, manager *game_user.PlayerManager, player *playbook.Player, user *model.User, data map[string]interface{}) error {
	if err := manager.UpdateUserStats(context, player, user, Center.StatData); err != nil {
		return err
	}

	if user.Badges == nil {
		user.Badges = make(map[int]*UserBadge)
	}

	if Center.UpdateBadge(user.Badges, user.StatInfo, user.OpenId) {
		if err := Center.ChangeBadge(user.Badges, data); err != nil {
			return err
		}
	}
	if err := Center.UpdateUser(user, data); err != nil {
		return err
	}
	return nil
}
